﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.IO;

namespace MarioArena.World
{
    class TileEngine
    {
        public Vector2 Playerstart { get; private set; }
        public Tile[,] Tiles { get; set; }

        /// <summary>
        /// Grid used for determining which area's are theoretically jumpable.
        /// </summary>
        public PathfindingTile[,] JumpingGrid { get; set; }

        public bool IsGridInitialized { get; private set; }

        private Texture2D mChocoBlock;      // 1
        private Texture2D mBrownBlock;      // 2
        private Texture2D mFaceBlockYellow; // 3
        private Texture2D mTouchedBlock;    // 4
        private Texture2D mStoneBlock;      // 5
        private Texture2D mCloudBlock;      // 6


        private Texture2D mDefaultTile;     // 3?

        private Texture2D mGrassTopLeft;    // 10
        private Texture2D mGrassTop;        // 11
        private Texture2D mGrassTopRight;   // 12
        private Texture2D mGrassLeft;       // 13
        private Texture2D mGrassMid;        // 14
        private Texture2D mGrassRight;      // 15

        private Texture2D mPipeTopLeft;     // 20
        private Texture2D mPipeTopRight;    // 21
        private Texture2D mPipeLeft;        // 22
        private Texture2D mPipeRight;       // 23
        private Texture2D mPipeBottomLeft;  // 24
        private Texture2D mPipeBottomRight; // 25

        private Texture2D mPipeLeftTop;     // 26
        private Texture2D mPipeLeftBottom;  // 27
        private Texture2D mPipeTop;         // 28
        private Texture2D mPipeBottom;      // 29
        private Texture2D mPipeRightTop;    // 30
        private Texture2D mPipeRightBottom; // 31

        //The list which contains the tilemap
        private List<string> mLines = new List<string>();

        //The positions to place the tiles
        private const int startPosX = 0;
        private const int startPoxY = 0;

        //The size of the tiles
        private const int width = 24;
        private const int height = 24;

        private int mScreenWidth = GameManager.GraphicsDevice.Viewport.Width;
        private int mScreenHeight = GameManager.GraphicsDevice.Viewport.Height;

        /// <summary>
        /// Width of the level measured in tiles.
        /// </summary>
        public int LevelWidth // TODO: AANPASSEN VOOR PERFORMANCE --> variabele eenmaal vastleggen -------------------------------------------------------------------------
        {
            get { return Tiles.GetLength(0); }
        }

        /// <summary>
        /// Height of the level measured in tiles.
        /// </summary>
        public int LevelHeight
        {
            get { return Tiles.GetLength(1); }
        }

        #region Singleton
        private static TileEngine instance = new TileEngine();
        public static TileEngine Instance
        {
            get
            {
                return instance;
            }
        }
        private TileEngine()
        {
            //Tiles = new Tile[0, 0];
        }
        #endregion

        
        /// <summary>
        /// Loads the tiles of the level.
        /// </summary>
        /// <param name="level">The level string received from the server.</param>
        /// <returns>Whether the tiles could be loaded correctly or not.</returns>
        public bool LoadTiles(string level)
        {
            int width = 0;
            int height = 0;
            string[] lines = level.Split('#');
            if (lines.Length == 0)
                return false; // Incorrect level given - level loading failed

            width = lines[0].Length;

            for (int i = 0; i < lines.Length; i++)
            {
                if (lines[i].Length != width)
                    return false; // The length of a line is incorrect - level loading failed
                height++;
            }

            Tiles = new Tile[width, height];

            // Loop over every tile position,
            for (int y = 0; y < height; ++y)
            {
                for (int x = 0; x < width; ++x)
                {
                    // to load each tile.
                    char tileType = lines[y][x];
                    LoadTile(tileType, x, y);
                }
            }

            return true;
        }

        public bool LoadTilesNew(string level)
        {
            // Level is received as following:
            // levelWidthInTiles:levelHeightInTiles:startIndexX#startIndexY#endIndexX#endIndexY#tileNumber|startIndexX#startIndexY#endIndexX#endIndexY#tileNumber
            // The tile of type 'tileNumber' starts at startIndexX and startIndexY, and is repeatedly 
            // places until endIndexX and endIndexY, creating a row of this tile type. Air is not considered a tile.

            try
            {
                string[] splitOnColon = level.Split(':');
                int levelWidth = Convert.ToInt32(splitOnColon[0]);
                int levelHeight = Convert.ToInt32(splitOnColon[1]);
                string[] tilePlacements = splitOnColon[2].Split('|');

                // Initialize the twodimensional array containing all tiles
                this.Tiles = new Tile[levelWidth, levelHeight];
                this.JumpingGrid = new PathfindingTile[levelWidth, levelHeight];

                foreach (string tilePlacement in tilePlacements)
                {
                    string[] values = tilePlacement.Split('#');
                    int startIndexX = Convert.ToInt32(values[0]);
                    int startIndexY = Convert.ToInt32(values[1]);
                    int endIndexX = Convert.ToInt32(values[2]);
                    int endIndexY = Convert.ToInt32(values[3]);
                    int tileNumber = Convert.ToInt32(values[4]);

                    for (int y = startIndexY; y <= endIndexY; y++)
                    {
                        for (int x = startIndexX; x <= endIndexX; x++)
                        {
                            LoadTileNew(tileNumber, x, y);
                        }
                    }
                }

                // Fill a separate grid with PathfindingTiles for pathfinding purposes
                for (int row = 0; row < this.LevelHeight; row++)
                {
                    for (int column = 0; column < this.LevelWidth; column++)
                    {
                        this.JumpingGrid[column, row] = new PathfindingTile(column, row, this.Tiles[column, row].IsWall);
                    }
                }

                // Generate unreachable tiles and adjacencies for each tile for pathfinding
                GenerateUnreachableTiles();
                GenerateEligibleTiles();
                GenerateAdjacencies();

                IsGridInitialized = true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error loading map: \n" + e);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Generates all unreachable tiles for the pathfinding grid.
        /// </summary>
        private void GenerateUnreachableTiles()
        {
            // PHASE 1: Set all tiles higher than 6 tiles above a walkable tile as UNREACHABLE
            // Loop the map from bottom to top
            //for (int row = this.LevelHeight - 1; row >= 0; row--)
            //{
            //    for (int column = 0; column < this.LevelWidth; column++)
            //    {
            //        if (row < this.LevelHeight - 7) // No need to check below this region, as its near the ground
            //        {
            //            PathfindingTile tile = this.JumpingGrid[column, row];
            //            bool allAirTiles = true;
            //            for (int i = 0; i < 7; i++) // Check if this tile AND 5 tiles down are all air tiles
            //            {
            //                if (this.JumpingGrid[column, row + i].IsWall)
            //                    allAirTiles = false;
            //            }

            //            // if all the 6 tiles below this tile are air tiles
            //            if (allAirTiles)
            //            {
            //                // Mark this tile as unreachable, because the player cannot reach this tile due to the maximum jump height
            //                tile.IsUnreachable = true;
            //            }
            //        }
            //    }
            //}

            // PHASE 2: For all unreachable tiles, mark the tile as REACHABLE if it can be reached by jumping
            // Loop the map
            for (int row = 1; row < LevelHeight; row++) // Skip the first row, because we only need to check the region above
            {
                for (int column = 0; column < this.LevelWidth; column++)
                {
                    Tile tile = this.Tiles[column, row];
                    if (tile.IsWall) // If the tile is not an air tile
                    {
                        int top = row - (Settings.JumpHeight + 1);
                        int left = column - (Settings.JumpDistance);
                        int bottom = row - 1; // Only check the region above the solid tile
                        int right = column + (Settings.JumpDistance);

                        if (top < 0) top = 0;
                        if (left < 0) left = 0;
                        if (bottom < 0) bottom = 0;
                        if (right >= LevelWidth) right = LevelWidth - 1;

                        for (int y = top; y <= bottom; y++)
                        {
                            int topDistance = row - top;
                            int extraTiles = (Settings.JumpHeight + 1) - topDistance; // topdistance of 0 results in 6 (which is on the bottom row), topdistance of 6 results in 0 (top row)
                            int newLeft = left - extraTiles;
                            int newRight = right + extraTiles;
                            if (newLeft < 0) newLeft = 0;
                            if (newRight >= LevelWidth) newRight = LevelWidth - 1;

                            for (int x = newLeft ; x <= newRight; x++)
                            {
                                PathfindingTile pathfindingTile = JumpingGrid[x, y];
                                pathfindingTile.IsUnreachable = false;
                            }
                        }
                        MakeOverhangUnreachable(column, row);
                    }
                }
            }
        }

        /// <summary>
        /// Marks overhangs as unreachable from the platforms below.
        /// </summary>
        /// <param name="column">The column of the tile to check for an overhang.</param>
        /// <param name="row">The row of the tile to check for an overhang.</param>
        private void MakeOverhangUnreachable(int column, int row)
        {
            int highestReachableTile = row - Settings.JumpHeight; // The highest tile reachable from this tile
            if (highestReachableTile < 0)
                highestReachableTile = 0;

            if (row - 1 >= 0) // If this row is not the top row in the map
            {
                // Check all tiles between this tile and the highest reachable tile
                for (int i = row - 1; i >= highestReachableTile; i--)
                {
                    if (Tiles[column, i].IsWall) // Wall detected above the tile and within jump height
                    {
                        int left = column - Settings.JumpDistance;
                        if (left < 0) left = 0;
                        int right = column + Settings.JumpDistance;
                        if (right >= LevelWidth) right = LevelWidth - 1;

                        // Mark all the tiles to the left side and right side within the jump distance of that tile as unreachable
                        for (int x = left; x < right; x++)
                        {
                            PathfindingTile tile = JumpingGrid[x, i];
                            if (!tile.IsWall)
                            {
                                if (!Tiles[x, row].IsWall)
                                    tile.IsUnreachable = true;
                            }
                        }
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// Generates which tiles are eligible for creating a path on. This prevents paths built in
        /// the air.
        /// </summary>
        private void GenerateEligibleTiles()
        {
            // Note: Eligible tiles are tiles that can actually be used to calculate a path via. Wall tiles are 
            // surrounded by eligible tiles. Wall tiles have one layer of eligible tiles on top, and 3 layers of 
            // eligible tiles to the left and right. Also, at the corner of a wall tile sticking out, a column 
            // of 3 eligible tiles is generated from the corner downwards.

            // PHASE 1: Check every wall tile (#) and check if its surrounding tiles (x) (except for the bottom) are
            // eligible for being a part of the generated path.
            // [ ] [ ] [ ] [ ] [ ]
            // [ ] [x] [x] [x] [x]
            // [ ] [x] [#] [#] [x]
            // [ ] [ ] [ ] [ ] [ ]
            // [#] [#] [#] [#] [#]

            // Loop through all rows except the top row
            for (int row = 1; row < LevelHeight; row++)
            {
                for (int column = 0; column < this.LevelWidth; column++)
                {
                    PathfindingTile wallTile = JumpingGrid[column, row];
                    if (wallTile.IsWall) // If tile is a reachable air tile
                    {
                        // Check the surrounding tiles (x) for this wall tile (#), except for the bottom
                        // [x] [x] [x]
                        // [x] [#] [x]
                        int left = column - 1;
                        if (left < 0) left = 0;
                        int right = column + 1;
                        if (right > LevelWidth - 1) right = LevelWidth - 1;

                        for (int i = left; i <= right; i++)
                        {
                            // [ ] [ ] [ ]
                            // [x] [#] [x]
                            PathfindingTile adjacentTile = JumpingGrid[i, row];
                            if (adjacentTile != wallTile && !adjacentTile.IsWall && !adjacentTile.IsUnreachable)
                            {
                                adjacentTile.IsEligible = true; // Mark this tile as eligible
                            }

                            // [x] [x] [x]
                            // [ ] [#] [ ]
                            if (row > 0)
                            {
                                adjacentTile = JumpingGrid[i, row - 1]; // Check the row above
                                if (!adjacentTile.IsWall && !adjacentTile.IsUnreachable)
                                {
                                    adjacentTile.IsEligible = true; // Mark this tile as eligible
                                }
                            }
                        }
                    }
                }
            }

            // PHASE 2: Check every eligible corner tile and extend the path to create paths across
            // gaps and to floating platforms.
            // [*] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [*] [ ] [ ] [ ] [ ] [ ] [ ] 
            // [*] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [*] [*] [*] [x] [x] [x] [x] 
            // [*] [ ] [ ] [ ] [*] [x] [x] [x] [x] [*] [*] [x] [#] [#] [x] 
            // [x] [x] [x] [x] [*] [x] [#] [#] [x] [ ] [ ] [*] [ ] [ ] [*] 
            // [x] [#] [#] [x] [ ] [x] [#] [#] [x] [ ] [ ] [*] [ ] [ ] [*] 
            // [x] [#] [#] [x] [x] [x] [#] [#] [x] [x] [x] [x] [x] [x] [x] 
            // [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] 

            // Loop through all rows except the top row (because we need at least 2 tiles of space in 
            // order to be able to stand on it)


            for (int row = 1; row < LevelHeight; row++)
            {
                for (int column = 0; column < this.LevelWidth; column++)
                {
                    PathfindingTile tile = JumpingGrid[column, row];
                    if (tile.IsWall) // Proceed to next iteration if this tile is a wall
                        continue;
                    if (row - 1 >= 0 && JumpingGrid[column, row - 1].IsWall) // Proceed to next iteration if tile above is a wall
                        continue;

                    // GENERATE TILES TO THE LEFT AND RIGHT FROM ALL WALL TILES

                    // Check if this eligible tile is to the left of a wall
                    if (column + 1 < LevelWidth && column - 1 >= 0 && JumpingGrid[column + 1, row].IsWall)
                    {
                        // Generate path heading to the left
                        // - 1 because the corner is already 1 tile away from the platform (the corner is in the air)
                        int left = column - (Settings.JumpDistance - 1);
                        if (left < 0) left = 0;
                        GenerateEligibleFromCorners(new Point(column - 1, row), new Point(left, row));
                    }

                    // Check if this eligible tile is to the right of a wall
                    if (column + 1 < LevelWidth && column - 1 >= 0 && JumpingGrid[column - 1, row].IsWall)
                    {
                        // Generate path heading to the right
                        // - 1 because the corner is already 1 tile away from the platform (the corner is in the air)
                        int right = column + (Settings.JumpDistance - 1);
                        if (right > LevelWidth - 1) right = LevelWidth - 1;
                        GenerateEligibleFromCorners(new Point(column + 1, row), new Point(right, row));
                    }

                    // Check if this eligible tile is to the TOP LEFT of a wall
                    if (column + 1 < LevelWidth && column - 1 >= 0 && row + 1 < LevelHeight && JumpingGrid[column + 1, row + 1].IsWall)
                    {
                        // Generate path heading to the left
                        // - 1 because the corner is already 1 tile away from the platform (the corner is in the air)
                        int left = column - (Settings.JumpDistance - 1);
                        if (left < 0) left = 0;
                        GenerateEligibleFromCorners(new Point(column - 1, row), new Point(left, row));
                    }

                    // Check if this eligible tile is to the TOP RIGHT of a wall
                    if (column + 1 < LevelWidth && column - 1 >= 0 && row + 1 < LevelHeight && JumpingGrid[column - 1, row + 1].IsWall)
                    {
                        // Generate path heading to the left
                        // - 1 because the corner is already 1 tile away from the platform (the corner is in the air)
                        int right = column + (Settings.JumpDistance - 1);
                        if (right >= LevelWidth) right = LevelWidth - 1;
                        GenerateEligibleFromCorners(new Point(column + 1, row), new Point(right, row));
                    }
                    
                    // Check if this tile is to the left of a platform
                    // Check if the tile to the right is a wall, and the tile to the top and to the top right aren't
                    if (column + 1 < LevelWidth && row - 1 >= 0 && JumpingGrid[column + 1, row].IsWall &&
                        !JumpingGrid[column, row - 1].IsWall && !JumpingGrid[column + 1, row - 1].IsWall)
                    {
                        int bottom = row + (Settings.JumpHeight - 2);
                        if (bottom >= LevelHeight) bottom = LevelHeight - 1;
                        GenerateEligibleFromCorners(new Point(column, row), new Point(column, bottom));
                    }

                    // Check if this tile is to the right of a platform
                    // Check if the tile to the left is a wall, and the tile to the top and to the top right aren't
                    if (column - 1 >= 0 && row - 1 >= 0 && JumpingGrid[column - 1, row].IsWall &&
                        !JumpingGrid[column, row - 1].IsWall && !JumpingGrid[column - 1, row - 1].IsWall)
                    {
                        int bottom = row + (Settings.JumpHeight - 2);
                        if (bottom >= LevelHeight) bottom = LevelHeight - 1;
                        GenerateEligibleFromCorners(new Point(column, row), new Point(column, bottom));
                    }
                }
            }
        }

        /// <summary>
        /// Generates eligible tiles from eligible corner tiles. Eligible tile generation stop as soon as 
        /// a wall or an unreachable tile is detected, preventing a path from going through a wall.
        /// </summary>
        /// <param name="from">The index of the tile to start generating from (this tile included).</param>
        /// <param name="to">The index of the tile to stop generating at (this tile included).</param>
        private void GenerateEligibleFromCorners(Point from, Point to)
        {
            // Ensure that the end index is always greater than the start index.
            Point start = new Point(from.X, from.Y);
            Point end = new Point(to.X, to.Y);
            if (from.X > to.X)
            {
                start.X = to.X;
                end.X = from.X;
            }
            if (from.Y > to.Y)
            {
                start.Y = to.Y;
                end.Y = from.Y;
            }

            // Loop both rows and columns, although the path is always either 
            // 1 column wide or one row tall.
            for (int row = start.Y; row <= end.Y; row++)
            {
                for (int column = start.X; column <= end.X; column++)
                {
                    PathfindingTile tile = JumpingGrid[column, row];
                    // Prevent a path from going through a wall or unreachable terrain
                    if (tile.IsUnreachable || tile.IsWall) 
                        return;
                    else
                        tile.IsEligible = true;
                }
            }
        }

        /// <summary>
        /// Generate the adjacencies for all tiles. The adjacencies are 
        /// used for pathfinding.
        /// </summary>
        private void GenerateAdjacencies()
        {
            for (int y = 0; y < LevelHeight; y++)
            {
                for (int x = 0; x < LevelWidth; x++)
                {
                    JumpingGrid[x, y].CalculateAdjacencies();
                }
            }
        }

        /// <summary>
        /// Unloads the tiles, so that new tiles can be loaded
        /// </summary>
        public void UnloadTiles()
        {
            mLines.Clear();
        }

        #region Tile Representations
        /// <summary>
        /// Loads a tile of a specific kind at position x, y
        /// </summary>
        /// <param name="tileType">The character of the tile.</param>
        /// <param name="x">The X-position of the tile</param>
        /// <param name="y">The Y-position of the tile</param>
        private void LoadTile(char tileType, int x, int y)
        {
            switch (tileType)
            {
                // Blank space
                case '.':
                    break;

                //Ground tiles
                case '~':
                    Tiles[x, y] = new Tile(mGrassTop, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '<':
                    Tiles[x, y] = new Tile(mGrassTopLeft, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '>':
                    Tiles[x, y] = new Tile(mGrassTopRight, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case ':':
                    Tiles[x, y] = new Tile(mGrassMid, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '/':
                    Tiles[x, y] = new Tile(mGrassLeft, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '\\':
                    Tiles[x, y] = new Tile(mGrassRight, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;

                // Pipe tiles
                case '[':
                    Tiles[x, y] = new Tile(mPipeLeft, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case ']':
                    Tiles[x, y] = new Tile(mPipeRight, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '{':
                    Tiles[x, y] = new Tile(mPipeTopLeft, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '}':
                    Tiles[x, y] = new Tile(mPipeTopRight, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;

                // Misc tiles
                case 'x':
                    Tiles[x, y] = new Tile(mDefaultTile, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '-':
                    Tiles[x, y] = new Tile(mBrownBlock, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;
                case '+': //Chocoblock
                    Tiles[x, y] = new Tile(mChocoBlock, new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height));
                    break;

                // Player and enemy tiles
                case 's': //Starting position of the player
                    // Position on screen
                    int initialPlayerPosX = (GameManager.GraphicsDevice.Viewport.Width / 2) - (Tile.Width / 2);
                    int initialPlayerPosY = (GameManager.GraphicsDevice.Viewport.Height / 2) - (Tile.Height / 2);
                    Playerstart = new Vector2(initialPlayerPosX, initialPlayerPosY);

                    // Camera position
                    Camera.X = (Camera.X - initialPlayerPosX) + (x * 24);
                    Camera.Y = (Camera.Y - initialPlayerPosY) + (y * 24);
                    break;
                // Unknown tile type character
                default:
                    throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, x, y));
            }
        }

        /// <summary>
        /// Places a tile with the correct texture at the correct position.
        /// </summary>
        /// <param name="tileType">The type of the tile.</param>
        /// <param name="indexX">The X-index of the tile.</param>
        /// <param name="indexY">The Y-index of the tile.</param>
        private void LoadTileNew(int tileType, int indexX, int indexY)
        {
            Rectangle tileBounds = new Rectangle(indexX * Tile.Width, indexY * Tile.Height, Tile.Width, Tile.Height);
            switch (tileType)
            {
                // Miscellaneous tiles
                case 1:
                    Tiles[indexX, indexY] = new Tile(mChocoBlock, tileBounds, true);
                    break;
                case 2:
                    Tiles[indexX, indexY] = new Tile(mBrownBlock, tileBounds, true);
                    break;
                case 3:
                    Tiles[indexX, indexY] = new Tile(mFaceBlockYellow, tileBounds, true);
                    break;
                case 4:
                    Tiles[indexX, indexY] = new Tile(mTouchedBlock, tileBounds, true);
                    break;
                case 5:
                    Tiles[indexX, indexY] = new Tile(mStoneBlock, tileBounds, true);
                    break;
                case 6:
                    Tiles[indexX, indexY] = new Tile(mCloudBlock, tileBounds, true);
                    break;

                // Ground tiles
                case 10:
                    Tiles[indexX, indexY] = new Tile(mGrassTopLeft, tileBounds, true);
                    break;
                case 11:
                    Tiles[indexX, indexY] = new Tile(mGrassTop, tileBounds, true);
                    break;
                case 12:
                    Tiles[indexX, indexY] = new Tile(mGrassTopRight, tileBounds, true);
                    break;
                case 13:
                    Tiles[indexX, indexY] = new Tile(mGrassLeft, tileBounds, true);
                    break;
                case 14:
                    Tiles[indexX, indexY] = new Tile(mGrassMid, tileBounds, true);
                    break;
                case 15:
                    Tiles[indexX, indexY] = new Tile(mGrassRight, tileBounds, true);
                    break;

                // Vertical pipe
                case 20:
                    Tiles[indexX, indexY] = new Tile(mPipeTopLeft, tileBounds, true);
                    break;
                case 21:
                    Tiles[indexX, indexY] = new Tile(mPipeTopRight, tileBounds, true);
                    break;
                case 22:
                    Tiles[indexX, indexY] = new Tile(mPipeLeft, tileBounds, true);
                    break;
                case 23:
                    Tiles[indexX, indexY] = new Tile(mPipeRight, tileBounds, true);
                    break;
                case 24:
                    Tiles[indexX, indexY] = new Tile(mPipeBottomLeft, tileBounds, true);
                    break;
                case 25:
                    Tiles[indexX, indexY] = new Tile(mPipeBottomRight, tileBounds, true);
                    break;

                // Horizontal pipe
                case 26:
                    Tiles[indexX, indexY] = new Tile(mPipeLeftTop, tileBounds, true);
                    break;
                case 27:
                    Tiles[indexX, indexY] = new Tile(mPipeLeftBottom, tileBounds, true);
                    break;
                case 28:
                    Tiles[indexX, indexY] = new Tile(mPipeTop, tileBounds, true);
                    break;
                case 29:
                    Tiles[indexX, indexY] = new Tile(mPipeBottom, tileBounds, true);
                    break;
                case 30:
                    Tiles[indexX, indexY] = new Tile(mPipeRightTop, tileBounds, true);
                    break;
                case 31:
                    Tiles[indexX, indexY] = new Tile(mPipeRightBottom, tileBounds, true);
                    break;

                // Player
                case 100: //Starting position of the player
                    int initialPlayerPosX = (GameManager.GraphicsDevice.Viewport.Width / 2) - (Tile.Width / 2);
                    int initialPlayerPosY = (GameManager.GraphicsDevice.Viewport.Height / 2) - (Tile.Height / 2);
                    this.Playerstart = new Vector2(initialPlayerPosX, initialPlayerPosY);
                    Camera.X = (indexX * 24) - initialPlayerPosX;
                    Camera.Y = (indexY * 24) - initialPlayerPosY;
                    break;
            }
        }
        #endregion

        /// <summary>
        /// Loads all content (textures) used in the TileEngine class
        /// </summary>
        public void LoadContent()
        {
            mDefaultTile = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/standardtile");
            
            mChocoBlock = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/chocoblock");             // 1
            mBrownBlock = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/blockbrown");             // 2
            mFaceBlockYellow = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/faceblockyellow");   // 3
            mTouchedBlock = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/touchedblock");         // 4
            mStoneBlock = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/stoneblock");             // 5
            mCloudBlock = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/cloudblock");             // 6
            
            mGrassTop = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grasstop");                 // 10
            mGrassTopLeft = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grasstopleft");         // 11
            mGrassTopRight = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grasstopright");       // 12
            mGrassMid = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grassmid");                 // 13
            mGrassLeft = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grassleft");               // 14
            mGrassRight = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/grassright");             // 15

            // Vertical pipe
            mPipeLeft = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipeleft");                 // 20
            mPipeRight = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/piperight");               // 21
            mPipeTopLeft = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipetopleft");           // 22
            mPipeTopRight = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipetopright");         // 23
            mPipeBottomLeft = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipebottomleft");     // 24
            mPipeBottomRight = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipebottomright");   // 25
            
            // Horizontal pipe
            mPipeLeftTop = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipelefttop");           // 26
            mPipeLeftBottom = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipeleftbottom");     // 27
            mPipeTop = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipetop");                   // 28
            mPipeBottom = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/pipebottom");             // 29
            mPipeRightTop = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/piperighttop");         // 30
            mPipeRightBottom = GameManager.Content.Load<Texture2D>(@"Sprites/Tiles/piperightbottom");   // 31
        }

        

        /// <summary>
        /// Draw the tiles visible to the player
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            // Indicates whether the player has vision of tiles below the bottom layer of tiles.
            // If so, the bottom should be filled up with ground tiles.
            bool visionBelowBottom = false; 
            int outOfRangeBottomIndex = -1; // Store the out of range bottom index, because we will use this for drawing the filling tiles.
 
            // -2 and +2 are off-screen
            int leftBorderTileIndex = Player.Instance.Character.TileIndex.X - ((mScreenWidth / 2) / Tile.Width) - 1;
            int rightBorderTileIndex = Player.Instance.Character.TileIndex.X + ((mScreenWidth / 2) / Tile.Width) + 1;
            int topBorderTileIndex = Player.Instance.Character.TileIndex.Y - ((mScreenHeight / 2) / Tile.Height) - 1;
            int bottomBorderTileIndex = Player.Instance.Character.TileIndex.Y + ((mScreenHeight / 2) / Tile.Height) + 1;

            // Make sure the indexes don't exceed the actual bounds of the map
            if (leftBorderTileIndex < 0) leftBorderTileIndex = 0;
            if (rightBorderTileIndex < 0) rightBorderTileIndex = 0;
            if (topBorderTileIndex < 0) topBorderTileIndex = 0;
            if (bottomBorderTileIndex < 0) bottomBorderTileIndex = 0;
            if (rightBorderTileIndex > LevelWidth - 1)
                rightBorderTileIndex = LevelWidth - 1;
            if (bottomBorderTileIndex > LevelHeight - 1)
            {
                outOfRangeBottomIndex = bottomBorderTileIndex;
                visionBelowBottom = true;
                bottomBorderTileIndex = LevelHeight - 1;
            }

            for (int x = leftBorderTileIndex; x <= rightBorderTileIndex; x++)
            {
                for (int y = topBorderTileIndex; y <= bottomBorderTileIndex; y++)
                {
                     //If there is a visible tile in that position
                    Texture2D texture = Tiles[x, y].Texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(Tiles[x, y].Bounds.X - Camera.X, Tiles[x, y].Bounds.Y - Camera.Y);
                        spriteBatch.Draw(texture, position, Color.White);
                    }

                    // Only draw the restricting tiles is AI-mode is enabled
                    if (Player.Instance.HasAiEnabled)
                    {
                        // draw the unreachable tiles
                        if (this.JumpingGrid[x, y].IsUnreachable)
                        {
                            Vector2 pos = new Vector2(x * Tile.Width - Camera.X, y * Tile.Height - Camera.Y);
                            spriteBatch.Draw(mDefaultTile, pos, Color.Red * 0.5f);
                        }

                        // draw the eligible tiles
                        if (this.JumpingGrid[x, y].IsEligible)
                        {
                            Vector2 pos = new Vector2(x * Tile.Width - Camera.X, y * Tile.Height - Camera.Y);
                            spriteBatch.Draw(mDefaultTile, pos, Color.Yellow * 0.5f);
                        }
                    }
                }
            }
            
            if (visionBelowBottom)
            {   // Fill the bottom of the sreen BELOW the bottom layer of tiles with ground tiles.
                for (int i = leftBorderTileIndex; i <= rightBorderTileIndex; i++)
                {
                    // Bottom border 
                    for (int j = LevelHeight; j <= outOfRangeBottomIndex; j++)
                    {
                        Vector2 position = new Vector2(i * Tile.Width - Camera.X, j * Tile.Height - Camera.Y);

                        if (i == leftBorderTileIndex)
                            spriteBatch.Draw(this.mGrassLeft, position, Color.White);  // Draw left corner ground tile
                        else if (i == rightBorderTileIndex)
                            spriteBatch.Draw(this.mGrassRight, position, Color.White); // Draw right corner ground tile
                        else spriteBatch.Draw(this.mGrassMid, position, Color.White);  // Draw mid ground tile
                    }
                }
            }
        }
    }
}
